package com.own.component.util.time;

import com.own.component.util.time.model.TimetableModel;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * TimetableUtil
 *
 * @author chenxueli
 * @date 2024-01-18 15:58:47
 */
@Slf4j
public final class TimetableUtil {

    /**
     * 验证有效性
     *
     * @param start 开始时间
     * @param end   结束时间
     * @return 是否有效
     */
    public static boolean check(LocalTime start, LocalTime end) {
        return end.isAfter(start);
    }

    /**
     * 判断时间是否有重叠
     *
     * @param start1 开始时间1
     * @param end1   结束时间1
     * @param start2 开始时间2
     * @param end2   结束时间2
     * @return 是否重叠
     */
    public static boolean overlay(LocalTime start1, LocalTime end1, LocalTime start2, LocalTime end2) {
        if (!check(start1, end1) || !check(start2, end2)) {
            return true;
        }
        // start1在start2之前且end1在start2之后
        if (start1.toSecondOfDay() < start2.toSecondOfDay() && end1.toSecondOfDay() > start2.toSecondOfDay()) {
            return true;
        }
        // end1在end2之后且start1在end2之前
        if (end1.toSecondOfDay() > end2.toSecondOfDay() && start1.toSecondOfDay() < end2.toSecondOfDay()) {
            return true;
        }
        // start1在start2之后且end1在end2之前
        return start1.toSecondOfDay() >= start2.toSecondOfDay() && end1.toSecondOfDay() <= end2.toSecondOfDay();
    }

    /**
     * 计算可用时间范围
     *
     * @param extent 需要排除的时间范围
     * @param list   可用的时间范围列表
     * @return 是否满足条件
     */
    public static boolean checkAvailableRange(TimetableModel.TimeExtent extent, List<Set<TimetableModel.TimeExtent>> list) {
        return checkAvailableRange(extent.getStart(), extent.getEnd(), list);
    }

    /**
     * 计算可用时间范围
     *
     * @param start 需要排除的开始时间
     * @param end   需要排除的结束时间
     * @param list  可用的时间范围列表
     * @return 是否满足条件
     */
    public static boolean checkAvailableRange(LocalTime start, LocalTime end, List<Set<TimetableModel.TimeExtent>> list) {
        var flag = false;
        for (var item : list) {
            for (var timetable : item) {
                // 需要排除时间的开始时间大于等于时间表的开始时间
                var flag11 = timetable.getStart().toSecondOfDay() <= start.toSecondOfDay();
                // 需要排除时间的结束时间小于等于时间表的结束时间
                var flag22 = timetable.getEnd().toSecondOfDay() >= end.toSecondOfDay();
                // 只用判断是否全部包含在里面（开始时间在开始时间之后，结束时间在结束时间之后）
                if (flag11 && flag22) {
                    // 直接暴力创建新的数据
                    TimetableUtil.appendExtentToList(item, new TimetableModel.TimeExtent(LocalTimeUtil.copy(timetable.getStart()), LocalTimeUtil.copy(start)));
                    TimetableUtil.appendExtentToList(item, new TimetableModel.TimeExtent(end, LocalTimeUtil.copy(timetable.getEnd())));
                    // 当前的item不需要了（强行直接移除）
                    item.remove(timetable);
                    // 需要表名当前的数据已经被用过了，需要跳出循环
                    flag = true;
                    break;
                }
            }
            if (flag) {
                break;
            }
        }
        return flag;
    }

    /**
     * 将数据添加进范围内
     *
     * @param list   范围列表
     * @param extent 时间范围
     */
    public static void appendExtentToList(List<TimetableModel.TimeExtent> list, TimetableModel.TimeExtent extent) {
        if (extent.available()) {
            list.add(extent);
        }
    }

    /**
     * 将数据添加进范围内
     *
     * @param list   范围列表
     * @param extent 时间范围
     */
    public static void appendExtentToList(Set<TimetableModel.TimeExtent> list, TimetableModel.TimeExtent extent) {
        if (extent.available()) {
            list.add(extent);
        }
    }

    /**
     * 合并数据
     *
     * @param list 时间列表
     * @return 新列表
     */
    public static Set<TimetableModel.TimeExtent> merge(List<Set<TimetableModel.TimeExtent>> list) {
        return merge(list.stream().flatMap(Collection::stream).collect(Collectors.toList()));
    }

    /**
     * 合并数据
     *
     * @param list 时间列表
     * @return 新列表
     */
    public static Set<TimetableModel.TimeExtent> merge(Collection<TimetableModel.TimeExtent> list) {
        var resultList = new HashSet<TimetableModel.TimeExtent>();
        list.stream()
                .distinct()
                .filter(Objects::nonNull)
                .filter(TimetableModel.TimeExtent::available)
                .forEach(extent -> handle(resultList, extent));
        return resultList;
    }

    /**
     * 可用时间范围处理
     *
     * @param list   已有时间范围列表
     * @param extent 待处理的可用时间范围
     */
    public static void handle(HashSet<TimetableModel.TimeExtent> list, TimetableModel.TimeExtent extent) {
        // 如果列表等于0
        if (list.isEmpty()) {
            list.add(extent.copy());
            return;
        }
        var addList = new ArrayList<TimetableModel.TimeExtent>();
        var flag = true;
        for (var item : list) {
            // 在开始时间之前
            if (extent.getEnd().toSecondOfDay() < item.getStart().toSecondOfDay()) {
                addList.add(extent.copy());
                continue;
            }
            // 在结束时间之后
            if (extent.getStart().toSecondOfDay() > item.getEnd().toSecondOfDay()) {
                addList.add(extent.copy());
                continue;
            }
            // 都在外面
            if (item.getStart().toSecondOfDay() >= extent.getStart().toSecondOfDay() && item.getEnd().toSecondOfDay() <= extent.getEnd().toSecondOfDay()) {
                item.setStart(extent.getStart());
                item.setEnd(extent.getEnd());
                flag = false;
                continue;
            }
            // 都在里面
            if (item.getStart().toSecondOfDay() <= extent.getStart().toSecondOfDay() && item.getEnd().toSecondOfDay() >= extent.getEnd().toSecondOfDay()) {
                flag = false;
                continue;
            }
            // 开始时间在里面，结束时间在外面
            if (item.getStart().toSecondOfDay() <= extent.getStart().toSecondOfDay()) {
                item.setEnd(extent.getEnd());
                flag = false;
                continue;
            }
            // 结束时间在里面，开始时间在外面
            if (item.getEnd().toSecondOfDay() >= extent.getEnd().toSecondOfDay()) {
                item.setStart(extent.getEnd());
                flag = false;
            }
        }
        if (flag) {
            list.addAll(addList);
        }
    }

}
